﻿using System;
using System.Collections.Generic;
using System.Linq;
using CodeParser;
using CodeParser.Expressions;
using CodeParser.Tokens;
using DirectXMetaData;
using EffectEd.HlslEditor;
using ICSharpCode.AvalonEdit.CodeCompletion;
using ICSharpCode.AvalonEdit.Document;

namespace EffectEd.CodeCompletion
{
    public class CodeCompletionProvider
        : ICodeCompletionProvider
    {

        private Parser _parser = null;

        public CodeCompletionSettings GetCompletion(string source, int position)
        {
            var eventargs = new CodeAssistanceConfigurationEventArgs();
            if (CodeAssistanceConfigurationRequested != null)
            {
                CodeAssistanceConfigurationRequested(this, eventargs);
                return GetCodeCompletionSettingsForPosition(source, position, eventargs.Configuration);
            }
            return null;
        }

        private CodeCompletionSettings GetCodeCompletionSettingsForPosition(string source, int position, Configuration codeAssistanceConfiguration)
        {
            if (_parser == null) _parser = new Parser();
            var parseResult = _parser.Parse(source);
            var filter = GetFilterSegment(parseResult.Tokens, position);
            var settings = new CodeCompletionSettings
                               {
                                   CompletionData = GetCodeCompletionForExpression(parseResult.TreeRoot.FindExpressionAt(position), codeAssistanceConfiguration, source.Substring(filter.Offset, filter.Length)),
                                   IdentifierSegment = filter
                               };
            return settings;
        }

        private ISegment GetFilterSegment(IList<Token> tokens, int position)
        {
            var token = GetIdentifierToken(tokens, position);
            return token != null
                       ? new TextSegment() {StartOffset = token.Index, Length = position - token.Index}
                       : new TextSegment() {StartOffset = position, Length = 0};
        }


        private Token GetIdentifierToken(IList<Token> tokens, int position)
        {
            for (var index = 0; index < tokens.Count; index++)
            {
                var token = tokens[index];
                if (token.Index < position && token.Index + token.Value.Length >= position && token.Type == TokenType.Identifier)
                {
                    return token;
                }
            }
            return null;
        }

        private IEnumerable<ICompletionData> GetCodeCompletionForExpression(Expression expression, Configuration codeAssistanceConfiguration, string filter)
        {
            return GetCodeCompletionForContext(expression.GetHierarchy(), codeAssistanceConfiguration, filter);
        }

        private IEnumerable<ICompletionData> GetCodeCompletionForContext(IList<string> currentContext, Configuration codeAssistanceConfiguration, string filter)
        {
            var completion = new List<ICompletionData>();
            var matches = GetMatches(currentContext, codeAssistanceConfiguration);
            if (matches != null)
            {
                foreach (var match in matches)
                {
                    completion.AddRange(GetFunctionCompletionData(match.IntrinsicFunctions));
                    completion.AddRange(GetKeywordCompletionData(match.Keywords));
                }
                var result = completion.AsQueryable();
                if (!string.IsNullOrWhiteSpace(filter)) result = result.Where(c => c.Text.StartsWith(filter));
                return result.OrderBy(c => c.Text);
            }
            return completion;
        }

        private IEnumerable<ICompletionData> GetKeywordCompletionData(IEnumerable<Keyword> keywords)
        {
            return keywords.Select(keyword => new KeywordCompletionData(keyword.Name, string.Format("{0}\n\n{1}", keyword.Description, keyword.Definition.Replace("|", "\n")))).Cast<ICompletionData>();
        }

        private IEnumerable<ICompletionData> GetFunctionCompletionData(IEnumerable<Function> intrinsicFunctions)
        {
            return intrinsicFunctions.Select(function => new FunctionCompletionData(function.Name, string.Format("{0}\n\n{1}", function.Description, function.Definition.Replace("|", "\n")))).Cast<ICompletionData>();
        }

        /// <summary>
        /// Finds all codecompletions from the configuration that match the current context. multiple matches with the same score are all returned.
        /// </summary>
        /// <param name="currentContext"></param>
        /// <param name="codeAssistanceConfiguration"></param>
        /// <returns></returns>
        private List<Context> GetMatches(IList<string> currentContext, Configuration codeAssistanceConfiguration)
        {
            var currentScore = int.MaxValue;
            List<Context> matches = null;
            foreach (var context in codeAssistanceConfiguration.Contexts)
            {
                var score = GetMatchScore(currentContext, context.ContextExpression.ToLower().Split('.'));
                if (score.HasValue)
                {
                    if (score.Value < currentScore)
                    {
                        matches = new List<Context> { context };
                        currentScore = score.Value;
                    }
                    else if (score.Value == currentScore)
                    {
                        matches.Add(context);
                    }
                }
            }
            return matches;
        }

        /// <summary>
        /// Calculates whether the requestedContext desciption matches the current context and how good.  The closer the last requested expression to the last current expression, the lower the score (=better)
        /// </summary>
        /// <param name="currentContext"></param>
        /// <param name="requestedContext"></param>
        /// <returns></returns>
        private int? GetMatchScore(IList<string> currentContext, IList<string> requestedContext)
        {
            var currentContextIndex = 0;
            var requestedContextIndex = 0;
            for (; currentContextIndex < currentContext.Count; currentContextIndex++)
            {
                if (requestedContext[requestedContextIndex] == currentContext[currentContextIndex].ToLower())
                {
                    requestedContextIndex++;
                    if (requestedContextIndex == requestedContext.Count)
                    {
                        return currentContext.Count - 1 - currentContextIndex;
                    }
                }
            }
            return null;
        }

        public EventHandler<CodeAssistanceConfigurationEventArgs> CodeAssistanceConfigurationRequested;

    }
}
